/**
 *  Copyright 2002-2009 the original author or authors.
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package foo.bar.wiki.security;

import foo.bar.wiki.domain.User;
import foo.bar.wiki.security.UserAccount;
import foo.bar.wiki.security.NoSuchAccountException;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.cache.Cache;
import org.springframework.orm.jpa.JpaCallback;
import org.springframework.orm.jpa.JpaTemplate;
import org.apache.commons.logging.LogFactory;
import org.apache.commons.logging.Log;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceException;
import javax.persistence.Query;
import java.util.List;
import java.util.Map;
import java.util.HashMap;

/**
 * An extension of {@link org.apache.shiro.realm.AuthorizingRealm} where we alter the way of getting
 * <ul>
 *  <li>{@link AuthenticationInfo}</li>
 *  <li>{@link AuthorizationInfo}</li>
 *  <li>Authorization cache key</li>
 * </ul>
 *
 * @author tmjee
 * @version $Date: 2009-03-14 14:53:07 +0800 (Sat, 14 Mar 2009) $ $Id$
 */
public class UserAccountRealm extends AuthorizingRealm {

    private static final Log LOG = LogFactory.getLog(UserAccountRealm.class);

    // Data will be set up in SetupService upon initialization
    public volatile static UserAccount ANONYMOUS_USER_ACCOUNT;

    private JpaTemplate template;

    UserAccountRealm(){}
    public UserAccountRealm(JpaTemplate template) {
        this.template = template;    
    }
    
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        final UsernamePasswordToken upToken = (UsernamePasswordToken) token;

        UserAccount account = (UserAccount) getAccount(upToken.getUsername());

        if (account == null) {
            throw new NoSuchAccountException("Account ["+account+"] doesn't exists");
        }
        if (account.isDisabled()) {
            throw new DisabledAccountException("Account ["+account+"] is disabled");
        }
        if (account.isLocked()) {
            throw new LockedAccountException("Account [" + account + "] is locked.");
        }
        if (account.isCredentialsExpired()) {
            String msg = "The credentials for account [" + account + "] are expired";
            throw new ExpiredCredentialsException(msg);
        }

        return account;
    }

    private Map m = new HashMap();

    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        Object cacheKey = getAuthorizationCacheKey(principals);
        Account account = ANONYMOUS_USER_ACCOUNT;
        if (cacheKey != null) {
            Cache cache = getAuthorizationCache();
            if (cache == null) {  // no cache
                LOG.warn("Expecting a cache for jSecurity, typically this will be ehcache, make sure it's in the classpath and also backport-util-concurrent");
                if (m.containsKey(cacheKey)) {
                    return (AuthorizationInfo) m.get(cacheKey);
                }
                else {
                    Account _account = getAccount((String) cacheKey);
                    if (_account != null) {
                        m.put(cacheKey, account);
                        account = _account;
                    }
                }
            }
            else { // found cache
                account = (Account) cache.get(cacheKey);
                if (account == null) {
                    account = getAccount((String) cacheKey);
                }
                if (account == null) {
                    account = ANONYMOUS_USER_ACCOUNT;
                }
            }
        }
        LOG.warn("******************* using account="+account);
        return account;
    }

    protected Object getAuthorizationCacheKey(PrincipalCollection principals) {
        User user = (User) principals.iterator().next();
        //User user = (User) principals.fromRealm(getName()).iterator().next();
        if (user != null) {
            return user.getUsername(); //returns the username
        }
        return null;
    }

    protected Account getAccount(final String username) {
        UserAccount account = (UserAccount) template.execute(new JpaCallback() {
            public Object doInJpa(EntityManager entityManager) throws PersistenceException {
                Query query = entityManager.createNamedQuery("User_findByUsername");
                query.setParameter("username", username);
                List result = query.getResultList();
                if (result.size() > 0) {
                    User user = (User) result.iterator().next();
                    return new UserAccount(user, getName());
                }
                return null;
            }
        });
        return account;
    }
}
